package q2d.netWrap;

/* Ncontroller:
 * 
 * - Obudowuje standardowe mechanizmy pakietu java.net.
 * - Pozwala odbierac wszystkie nadchodzace pakiety ( w oddzielnym watku ).
 * - Zawiera kolejke, do ktorej wkladane sa wszystkie przechwycone pakiety.
 * - Inne obiekty moga zdejmowac pakiety z kolejki w celu ich obslugi.
 * - Pozwala na wysylanie obiektow w postaci pakietow.
 * - Pozwala na interpretacje pakietow jako obiekty.
 * 
 * Uzycie: 
 * - Ncontroller.Ncontroller( port ) : konstruktor, ktory przyporzadkowuje kontrolerowi port dla socketa.
 * - Ncontroller.listen() : uruchomienie nowego watku nasluchiwania pakietow.
 * - Ncontroller.close() : zakonczenie nasluchiwania ( zamyka tez socket ).
 *  
 */
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.*;
import java.util.concurrent.ConcurrentLinkedQueue;

public class Ncontroller {
	/* 
	 * Konstruktor. Nalezy podac numer portu. Zostanie utworzony socket datagramowy
	 * przyporzadkowany do tego numeru portu.
	 */
	public Ncontroller(int portNumber) {
		try {
			socket = new DatagramSocket( portNumber );
			//Wstepnie ustawiony timeout. Przerywa nasluchiwanie na sockecie, aby
			//petla Receivera mogla zobaczyc ewentualny warunek branch == quit.
			socket.setSoTimeout(soTimeout);
			//Raczej zostawic na defaulcie.
			socket.setSendBufferSize(bufferSize);			//TODO: wywalic
			socket.setReceiveBufferSize(bufferSize);			//TODO: wywalic
			sendSleep = defaultSendSleep;
			branch = 0;
			seqNumber = 0;
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/* 
	 * Konstruktor. Nalezy podac numer portu. Zostanie utworzony socket datagramowy
	 * przyporzadkowany do tego numeru portu.
	 */
	public Ncontroller(int portNumber, int senderSleepTime) {
		try {
			socket = new DatagramSocket( portNumber );
			//Wstepnie ustawiony timeout. Przerywa nasluchiwanie na sockecie, aby
			//petla Receivera mogla zobaczyc ewentualny warunek branch == quit.
			socket.setSoTimeout(soTimeout);
			//Raczej zostawic na defaulcie.
			socket.setSendBufferSize(bufferSize);			//TODO: wywalic
			socket.setReceiveBufferSize(bufferSize);			//TODO: wywalic
			sendSleep = senderSleepTime;
			branch = 0;
			seqNumber = 0;
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/* 
	 * Rozpocznij odbieranie pakietow w nowym watku. Odebrane pakiety
	 * beda umieszczane w kolejce. 
	 */
	public void listen() {
		Receiver r = new Receiver();
		Sender s = new Sender();
	    Thread rt = new Thread(r);
	    Thread st = new Thread(s);
		rt.start();
		st.start();
	}
	/* 
	 * Pobierz glowe kolejki (zostanie usunieta). Zwroc zdjety z kolejki
	 * Npakiet. 
	 */
	public Npacket pop() {
		return recvQueue.poll();
	}
	/* Metoda testowa: wypisuje elementy kolejki */
	public void printQueue() {
		Npacket[] dwin =  new Npacket[1000];
		Npacket[] dwout = recvQueue.toArray(dwin);
		TestClassA testA;
		TestClassB testB;
		Nwrapper wrapper;
		for(int i = 0; dwout[i] != null; i++) {
			//System.out.println(dwout[i].getId());
			//if(dwout[i].getId() == 8) close();
			wrapper = (Nwrapper)retrieveObject(dwout[i]);
			if(wrapper.getType() == 1){
				testA = (TestClassA)wrapper.getObject();
				System.out.print("A: ");
				System.out.print(testA.getId());
				System.out.print(testA.getX());
				System.out.print(testA.getY());
				System.out.println();
			} 
			else { 
				testB = (TestClassB)wrapper.getObject();
				System.out.print("B:");
				System.out.print(testB.getId());
				System.out.print(testB.getX());
				System.out.print(testB.getY());
				System.out.println();
			}
		}		
	}
	/* 
	 * Ustaw zmienna branch na warunek quit (branch == 1).
	 * Watek naslugu (Receiver) zostanie zamkniety prze nastepnym
	 * obiegu swojej petli.
	 */
	public void close() {
		branch = quit;
	}
	/* 
	 * Klasa wewnetrzna. Jest to watek, ktory w petli przyjmuje
	 * pakiety i wklada ja do kolejki FIFO.
	 */
	class Receiver implements Runnable {
		private Npacket received;
		//Numer sekwencyjny - jeszcze nie ma zastosowania.
		int sequence = 0;
		//Watek nie moze spac - gubilby pakiety.
		public void run() {
			while(branch != quit){
				try {
					inData = new byte[dataSize];
					inPacket = new DatagramPacket(inData, inData.length);
				
					socket.receive(inPacket);
						
					received = new Npacket(sequence, inPacket);
					sequence++;
					recvQueue.offer(received);
				} catch (IOException e) {
					//e.printStackTrace();
				}
			}
			socket.close();
		}
	}
	/* Klasa wewnetrzna. Jest to watek, ktory w petli wysyla zadane pakiety */
	class Sender implements Runnable {
		private Npacket toSend;
		public void run() {
			while(branch != quit){
				try {
					toSend = sendQueue.poll();
					if(toSend != null) {
						socket.send(toSend.getPacket());
					}
					Thread.sleep(sendSleep);
				} catch (IOException e) {
					//e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	/*
	 * Metoda przyjmuje obiekt do wyslania, typ obiektu zgodny z "globalnymi" ustaleniami
	 * aplikacji, adres docelowy i docelowy numer portu.
	 * UWAGA: wyslany zostaje obiekt Nwrapper, ktory opakowuje podany obiekt. Przed rzutowaniem
	 * nalezy sprawdzic typ obiektu ( Nwrapper.type ).
	 */
	public void sendObject(Object obj, int type, InetAddress address, int port) {
		Nwrapper wrapper = new Nwrapper(type, obj);
		sendWrapper(wrapper, address, port);
	}
	/*
	 * Metoda prywatna. Wykorzystywana przez Ncontroller.sendObject(...). Przyjmuje obiekt
	 * ( typu Nwrapper ), przetwarza go na ciag bajtow i wysyla pod wskazany adres docelowy.
	 */
	private void sendWrapper(Object obj, InetAddress address, int port ){
		try{
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(obj);
			byte[] objectData = new byte[dataSize];
			objectData = baos.toByteArray();			
			outPacket = new DatagramPacket(objectData, objectData.length, address, port);
			sendQueue.add(new Npacket(seqNumber, outPacket));
			seqNumber++;
			baos.close();
			oos.close();
		} catch (IOException e) {
		}
	}
	/*
	 * Metoda przyjmuje Npakiet. Zwracany jest zbudowany ze strumienia bajtow przeslanych pakietem obiekt.
	 * UWAGA: W logice aplikacji wysylanymi obiektami sa tylko Nwrappery. 
	 * Uzycie:
	 *     - Rzutowac zwracany obiekt na obiekt klasy Nwrapper.
	 *     - Sprawdzic pole Nwrapper.type w celu ustalenia faktycznego typu obiektu.
	 *     - Rzutowac obiekt Nwrapper.object na odpowiedni typ.
	 */
	public Object retrieveObject(Npacket packet){
		Object obj;
		try {
			byte[] objectData = packet.getData();
			ByteArrayInputStream baos = new ByteArrayInputStream(objectData);
			ObjectInputStream in;
			in = new ObjectInputStream(baos);
			obj = in.readObject();
			baos.close();
			in.close();
			return obj;
		} catch (IOException e) {
		} catch (ClassNotFoundException e) {
		}
		return null;
	}
	private static final int quit = 1;
	private static final int defaultSendSleep = 10;
	private static final int soTimeout = 10;
	private static final int bufferSize = 32768;
	private static final int dataSize = 4096;
	private int sendSleep;
	private long seqNumber; 			// niepotrzebny narazie
	private ConcurrentLinkedQueue<Npacket> recvQueue = new ConcurrentLinkedQueue<Npacket>();
	private ConcurrentLinkedQueue<Npacket> sendQueue = new ConcurrentLinkedQueue<Npacket>();
	private DatagramSocket socket;
	private DatagramPacket inPacket;
	private DatagramPacket outPacket;
	private byte[] inData;
	private byte[] outData;
	private int branch;					// initial branch
}
